/*
  Part of the MasterTool3D project - http://code.google.com/p/master-tool-3d

  Copyright (c) 2011 Ingo Pueschl

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation, version 2.1.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

package techjit.gfx.core;

import java.util.LinkedList;

import processing.core.PApplet;
import techjit.gfx.util.PU;
import techjit.gfx.util.geo.Dimensions;
import techjit.gfx.util.geo.Vector;
import techjit.util.LogManager;
import techjit.util.RuntimeParams;

public class GFX_CircleLayout extends GFX_Layout {

	// FIELDS
	// head
	private int circleLayoutId;
	private static int nextCircleLayoutId = RuntimeParams.GFX__FIRST_ID;

	// body
	private LinkedList<Float> phiList;
	private float radius;
	private float maxDepth;

	private float circleMidPointX;
	private float circleMidPointZ;

	private float chord; // kreissehne
	private boolean phiSumLessThan360 = false;
	private float TOLERANCE = 2;
	private float alpha;

	// CONSTRUCTORS
	public GFX_CircleLayout(GFX_Scene rootScene) {
		// head
		super(rootScene);
		circleLayoutId = nextCircleLayoutId++;

		// tail
		logConstructorResult();
	}

	// METHODS
	@Override
	protected void arrange() {

		// head
		logArrangementInvocation();

		// arrange
		arrange_arrangeElements();

		// dimensions
		arrange_determineLocalDimensions();

		// translate
		arrange_translateElements();

		// determine relDims
		super.arrange();

		// tail
		logLocalArrangementResult();
	}

	// METHODS
	private void arrange_arrangeElements() {
		for (GFX_SceneElement element : elements) {
			element.arrange();
		}
	}

	private void arrange_determineLocalDimensions() {
		if (elements.isEmpty()) {
			changeLocalDimensions(new Dimensions(4, 5, 6));
		} else if (elements.size() == 1) {
			GFX_SceneElement e1 = elements.get(0);
			Dimensions newLocalDims = new Dimensions(e1.relDims);
			changeLocalDimensions(newLocalDims);
		} else if (elements.size() == 2) {
			GFX_SceneElement e1 = elements.get(0);
			GFX_SceneElement e2 = elements.get(1);
			float w1 = e1.relDims.getWidth();
			float w2 = e2.relDims.getWidth();
			float h1 = e1.relDims.getHeight();
			float h2 = e2.relDims.getHeight();
			float d1 = e1.relDims.getDepth();
			float d2 = e2.relDims.getDepth();
			Dimensions newLocalDims = new Dimensions(PApplet.max(w1, w2),
					PApplet.max(h1, h2), d1 + d2);
			changeLocalDimensions(newLocalDims);
		} else {
			float layoutHeight = 0;
			maxDepth = 0f;
			float maxWidth = 0f;

			for (GFX_SceneElement element : elements) {
				layoutHeight = PApplet.max(layoutHeight,
						element.relDims.getHeight());
				maxWidth = PApplet.max(maxWidth, element.relDims.getWidth());
				maxDepth = PApplet.max(maxDepth, element.relDims.getDepth());
			}

			float newR = maxWidth / 2;
			float sum;
			do {
				sum = 0;
				phiList = new LinkedList<Float>();
				// LM.logTM("sum|newR = " + sum + "|" + newR);
				for (GFX_SceneElement element : elements) {
					float phi = PApplet.degrees(2 * PApplet
							.asin(element.relDims.getWidth() / (2 * newR)));
					phiList.add(phi);
					sum += phi;
				}
				// LM.logTM("sum|newR = " + sum + "|" + newR);

				newR += .001f;

			} while (sum > 360);// sum > 360);
			radius = newR;
			// LM.logTM("radius = " + radius);

			float phiSum = 0;
			for (float phi : phiList) {
				// LM.logTM("phi = " + phi);
				phiSum += phi;
			}
			phiSumLessThan360 = phiSum < 360 - TOLERANCE;
			// LM.logTM("phiSum = " + phiSum);

			if (phiSumLessThan360) {
				// TODO (tamas) P1 implement
				// TODO: (tamas) reduce the local dimensions as much as possible
				float layoutWidth = 2 * radius + 2 * maxDepth;
				float layoutDepth = 2 * radius + 2 * maxDepth;
				changeLocalDimensions(new Dimensions(layoutWidth, layoutHeight,
						layoutDepth));

				chord = 2 * radius * PU.sin((phiSum - 180) / 2);
				alpha = PApplet.degrees(PApplet.acos(chord / (2 * radius)));

				// LM.debugTM("alpha = "+ alpha);
				circleMidPointX = radius + maxDepth;
				circleMidPointZ = radius + maxDepth - radius * PU.sin(alpha);

			} else {

				float layoutWidth = 2 * radius;
				float layoutDepth = 2 * radius;

				// if (alignmentZ == AlignmentEnum.CENTER) {
				// //TODO: (tamas) check
				// //layoutDepth += maxDepth/2;
				// } else if (alignmentZ == AlignmentEnum.REMOTE) {
				layoutWidth += 2 * maxDepth;
				layoutDepth += 2 * maxDepth;
				// }

				changeLocalDimensions(new Dimensions(layoutWidth, layoutHeight,
						layoutDepth));
				circleMidPointX = maxDepth + radius;
				circleMidPointZ = maxDepth + radius;
			}
		}
	}

	private void arrange_translateElements() {

	}

	@Override
	protected void draw(PApplet p) {
		super.draw(p);
		p.pushMatrix();
		p.pushStyle();
		{
			if (elements.size() == 1) {
				draw_drawElement(p, elements.get(0));

			} else if (elements.size() == 2) {
				GFX_SceneElement e1 = elements.get(0);
				GFX_SceneElement e2 = elements.get(1);
				float w1 = e1.relDims.getWidth();
				float w2 = e2.relDims.getWidth();
				// TODO (ingo) P9 rel dims height should be determined here.
				// float h1 = e1.relDims.getHeight();
				// float h2 = e2.relDims.getHeight();
				float d1 = e1.relDims.getDepth();
				float d2 = e2.relDims.getDepth();

				if (w1 <= w2) {
					PU.translate(p, new Vector(w2, 0, d1 + d2));
					PU.rotateY(p, 180);
					draw_drawElement(p, elements.get(0));
					PU.translate(p, new Vector(w1 / 2 + w2 / 2, 0, d1 + d2));
					PU.rotateY(p, 180);
					draw_drawElement(p, elements.get(1));

				} else {
					draw_drawElement(p, elements.get(0));
					PU.translate(p, new Vector(w1 / 2 + w2 / 2, 0, d1 + d2));
					PU.rotateY(p, 180);
					draw_drawElement(p, elements.get(1));

				}

			} else if (phiSumLessThan360) {
				PU.translate_activateTrace();
				draw_drawTraceCircle(p);
				// TODO (tamas) P1 Implement.
				float maxWidth = 0f;
				int maxWidthIndex = 0;
				for (int i = 0; i < elements.size(); i++) {
					if (maxWidth < elements.get(i).relDims.getWidth()) {
						maxWidth = elements.get(i).relDims.getWidth();
						maxWidthIndex = i;
					}
				}

				PU.translate(p, new Vector(maxDepth, 0, maxDepth + radius
						- elements.get(maxWidthIndex).relDims.getDepth()));
				draw_drawElement(p, elements.get(maxWidthIndex));
				PU.translate(
						p,
						new Vector(radius + chord / 2, 0, elements
								.get(maxWidthIndex).relDims.getDepth()));
				int j = maxWidthIndex == elements.size() - 1 ? 0
						: maxWidthIndex + 1;
				PU.rotateY(p, -90);
				PU.rotateY(p, (180 - phiList.get(j)) / 2);
				PU.rotateY(p, -alpha);
				PU.rotateY(p, -90);

				// PU.rotateY(p, -(90-alpha));

				PU.translate(p,
						new Vector(0, 0, -elements.get(j).relDims.getDepth()));

				for (int i = j; i < j + elements.size() - 1; i++) {
					GFX_SceneElement element = elements.get(i
							% (elements.size()));
					draw_drawElement(p, element);
					PU.translate(p, new Vector(element.relDims.getWidth(), 0,
							element.relDims.getDepth()));

					float alpha = (phiList.get(i % (elements.size())) + phiList
							.get((i + 1) % (elements.size()))) / 2;

					PU.rotateY(p, -alpha);
					PU.translate(
							p,
							new Vector(0, 0, -elements.get((i + 1)
									% (elements.size())).relDims.getDepth()));
				}

				PU.translate_deactivateTrace();
			} else {
				draw_drawElements(p);
			}
		}
		p.popMatrix();
		p.popStyle();
	}

	/**
	 * @param p
	 * @param element
	 *            draws the element differently according to the Y Alignment
	 */
	private void draw_drawElement(PApplet p, GFX_SceneElement element) {
		if (alignmentY == GFX_Layout.AlignmentEnum.ADJACENT) {
			PU.translate_deactivateTrace();
			element.draw(p);
			PU.translate_activateTrace();
		} else if (alignmentY == GFX_Layout.AlignmentEnum.CENTER) {
			PU.translate(p, new Vector(0,
					(localDims.getHeight() - element.relDims.getHeight()) / 2,
					0));
			element.draw(p);
			PU.translate(
					p,
					new Vector(0, -((localDims.getHeight() - element.relDims
							.getHeight()) / 2), 0));
		} else if (alignmentY == GFX_Layout.AlignmentEnum.REMOTE) {
			PU.translate(p, new Vector(0, localDims.getHeight()
					- element.relDims.getHeight(), 0));
			element.draw(p);
			PU.translate(p, new Vector(0,
					-(localDims.getHeight() - element.relDims.getHeight()), 0));
		}

	}

	private void draw_drawElements(PApplet p) {
		/**
		 * translate the local coordinate system where the first element is to
		 * be drawn
		 */
		float phi0 = phiList.get(0);
		PU.translate_activateTrace();
		draw_drawTraceCircle(p);
		PU.translate(p, new Vector(maxDepth + radius, 0, maxDepth + radius));
		PU.rotateY(p, (90 + phi0 / 2));
		PU.translate(p, new Vector(radius, 0, 0));
		PU.rotateY(p, (-phi0 / 2));
		PU.translate(p, new Vector(elements.get(0).relDims.getDepth(), 0, 0));
		PU.rotateY(p, -90);

		for (int i = 0; i < elements.size(); i++) {
			/** draw the current element */
			GFX_SceneElement element = elements.get(i);
			draw_drawElement(p, element);

			/**
			 * translate the local coordinate system where the next element is
			 * to be drawn and rotate it properly
			 */
			float wi = element.relDims.getWidth();
			float di = element.relDims.getDepth();
			PU.translate(p, new Vector(wi, 0, di));

			float followingPhi = i < elements.size() - 1 ? phiList.get(i + 1)
					: phiList.get(0);
			float alpha = (phiList.get(i) + followingPhi) / 2;
			PU.rotateY(p, 90 - alpha);

			GFX_SceneElement followingElement = i < elements.size() - 1 ? elements
					.get(i + 1) : elements.get(0);
			PU.translate(p, new Vector(followingElement.relDims.getDepth(), 0,
					0));
			PU.rotateY(p, -90);
		}
		PU.translate_deactivateTrace();
	}

	private void draw_drawTraceCircle(PApplet p) {
		if ((elements != null) && (elements.size() >= 3)
				&& RuntimeParams.SHOW__CIRCLE_LAYOUT_TRACE_CIRCLE) {
			p.pushStyle();
			p.pushMatrix();
			{
				p.noFill();
				p.stroke(RuntimeParams.COLOR__RED);
				PU.translate(p, new Vector(circleMidPointX - radius, 0,
						circleMidPointZ - radius));
				PU.ellipse(p, new Vector(radius * 2, 0, radius * 2));
			}
			p.popMatrix();
			p.popStyle();
		}
	}

	@Override
	public void log() {
		LogManager.logALL("" + this);
		LogManager.indent();
		{
			super.log();
		}
		LogManager.deindent();
	}

	public GFX_Node navi_getFirstChildNode() {
		GFX_LineLayout firstChildLineLayout = (GFX_LineLayout) elements.get(0);
		return (GFX_Node) firstChildLineLayout.elements.get(0);
	}

	public GFX_Node navi_getLastChildNode() {
		GFX_LineLayout lastChildLineLayout = (GFX_LineLayout) elements
				.get(elements.size() - 1);
		return (GFX_Node) lastChildLineLayout.elements.get(0);
	}

	public GFX_Node navi_getNextChildNode(GFX_LineLayout child) {
		int index = elements.indexOf(child);
		int lastIndex = elements.size() - 1;
		if (index == lastIndex) {
			return (GFX_Node) child.navi_getFirstChild();
		} else {
			GFX_LineLayout nextChildLineLayout = (GFX_LineLayout) elements
					.get(index + 1);
			return (GFX_Node) nextChildLineLayout.navi_getFirstChild();
		}
	}

	public GFX_Node navi_getPreviousChildNode(GFX_LineLayout child) {
		int index = elements.indexOf(child);
		if (index == 0) {
			return (GFX_Node) child.navi_getFirstChild();
		} else {
			GFX_LineLayout nextChildLineLayout = (GFX_LineLayout) elements
					.get(index - 1);
			return (GFX_Node) nextChildLineLayout.navi_getFirstChild();
		}
	}

	@Override
	public String toString() {
		return "<GFX_CircleLayout " + circleLayoutId + ">";
	}

}
